fix(dates): allow clearing issue, cycle, and module dates - #255
Conversation
Once a start/target/end date was set it could not be cleared: the handlers only parsed the field when it was a non-empty string, so both null and "" collapsed to "no change" and the service left the value untouched. The date fields on the issue, cycle, and module PATCH endpoints now bind as json.RawMessage and go through a shared parseUpdatableDate helper with explicit tri-state semantics: an absent key leaves the value unchanged, null or "" clears it to NULL, and a YYYY-MM-DD (or RFC3339) value sets it. The services take a matching "set" flag per date and assign the pointer (nil clears) only when set, mirroring the existing estimate_point_id pattern. Closes #126 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strix Security ReviewNo security issues found. Updated for Reviewed by Strix |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis change adds PATCH semantics that distinguish absent, null, blank, and valued date fields for issues, cycles, and modules. Handlers pass explicit set flags to services, services clear fields when values are null, and tests verify persisted NULLs. ChangesClearable date fields
Estimated code review effort: 4 (Complex) | ~55 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant Handler
participant parseUpdatableDate
participant Service
participant Database
Client->>Handler: PATCH {date: null}
Handler->>parseUpdatableDate: parse raw JSON value
alt field absent
parseUpdatableDate-->>Handler: set=false
Handler->>Service: Update(dateSet=false)
Service->>Database: leave date unchanged
else field null or blank
parseUpdatableDate-->>Handler: set=true, value=nil
Handler->>Service: Update(dateSet=true, date=nil)
Service->>Database: set date = NULL
else field has value
parseUpdatableDate-->>Handler: set=true, value=parsedTime
Handler->>Service: Update(dateSet=true, date=parsedTime)
Service->>Database: set date = parsedTime
end
Database-->>Service: persisted
Service-->>Handler: updated entity
Handler-->>Client: 200 OK
Related Issues: Estimated code review effort: 4 (Complex) | ~55 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies" Comment |
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/api/internal/handler/clear_dates_test.go (1)
52-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCycle and module tests skip the "omit field leaves unchanged" case.
TestIssue_ClearDatesverifies all three states (set, clear-via-null, omitted-leaves-unchanged), butTestCycle_ClearDatesandTestModule_ClearDatesonly cover set and clear. The PR objective explicitly calls for tests covering "set, update, and clear behavior" for all three entities, and the omitted-field case is the one most likely to regress silently since it depends on the*Setflag correctly stayingfalse.✅ Suggested addition for TestCycle_ClearDates
var cleared model.Cycle require.NoError(t, ts.DB.First(&cleared, "id = ?", cy.ID).Error) require.Nil(t, cleared.StartDate, "cycle start_date should clear") require.Nil(t, cleared.EndDate, "cycle end_date should clear") + + // Omitting the field leaves it unchanged. + require.Equal(t, http.StatusOK, ts.PATCH(base, map[string]any{"start_date": "2026-04-01"}, w.Session).Code) + require.Equal(t, http.StatusOK, ts.PATCH(base, map[string]any{"name": "Renamed"}, w.Session).Code) + var kept model.Cycle + require.NoError(t, ts.DB.First(&kept, "id = ?", cy.ID).Error) + require.NotNil(t, kept.StartDate, "omitting start_date must not clear it") }A similar addition applies to
TestModule_ClearDates.Also applies to: 76-98
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/api/internal/handler/clear_dates_test.go` around lines 52 - 74, TestCycle_ClearDates currently covers only setting dates and clearing them with null, but it is missing the “omitted field leaves value unchanged” case. Extend TestCycle_ClearDates to perform an update where start_date and end_date are not included, then assert the previously stored dates remain unchanged by reloading the cycle via model.Cycle; apply the same pattern to TestModule_ClearDates using the corresponding handler test and model.Module so all three states are verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/api/internal/handler/cycle.go`:
- Line 154: The cycle PATCH handler is ignoring JSON binding errors, so
malformed payloads and bad field types can slip through as no-op updates. Update
the cycle update flow in the handler that calls c.ShouldBindJSON on body to
check and return a 400 response when binding fails, instead of discarding the
error. Use the existing PATCH handling path in the cycle handler to keep valid
requests unchanged while rejecting invalid request bodies early.
In `@apps/api/internal/service/issue.go`:
- Around line 574-578: The date-change logging in issue updates is skipping
clear events because the history/activity checks still gate on
startDate/targetDate being non-nil even when startDateSet or targetDateSet is
true. Update the logic around issue.StartDate and issue.TargetDate in the issue
update flow so the logging/notification conditions use the corresponding set
flags, allowing nil assignments to be recorded as clears. Use the existing
startDateSet and targetDateSet checks alongside the date fields in the issue
update path to locate and adjust the change-tracking conditions.
---
Nitpick comments:
In `@apps/api/internal/handler/clear_dates_test.go`:
- Around line 52-74: TestCycle_ClearDates currently covers only setting dates
and clearing them with null, but it is missing the “omitted field leaves value
unchanged” case. Extend TestCycle_ClearDates to perform an update where
start_date and end_date are not included, then assert the previously stored
dates remain unchanged by reloading the cycle via model.Cycle; apply the same
pattern to TestModule_ClearDates using the corresponding handler test and
model.Module so all three states are verified.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 37bd387d-f76f-47e0-8b39-e479883d49e0
📒 Files selected for processing (9)
apps/api/internal/handler/clear_dates_test.goapps/api/internal/handler/cycle.goapps/api/internal/handler/dates.goapps/api/internal/handler/epic.goapps/api/internal/handler/issue.goapps/api/internal/handler/module.goapps/api/internal/service/cycle.goapps/api/internal/service/issue.goapps/api/internal/service/module.go
CodeRabbit on PR #255: - the issue activity/notification log gated start_date/target_date changes on the pointer being non-nil, so clearing a date was silently omitted from history; gate on the set flag instead so clears are recorded. - the cycle Update handler ignored the JSON bind error, letting a malformed body fall through as a no-op; return 400 for bad JSON while still allowing an empty body, matching the module handler. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nazarli-shabnam
left a comment
There was a problem hiding this comment.
max acc privileges. jealous
What
Closes #126. Once a start/target/end date was set on a work item, cycle, or module, it could not be cleared. The handlers only parsed the date when it was a non-empty string, so both
nulland""collapsed to "no change" and the service left the value untouched.How
json.RawMessageand go through a sharedparseUpdatableDatehelper with explicit tri-state semantics:nullor""-> clear to NULL"YYYY-MM-DD"(or RFC3339) -> setUpdatetakes a matching...Set boolflag per date and assigns the pointer (nil clears) only when set, mirroring the existingestimate_point_idpattern. Callers (bulk update, epic update) passfalse, nilfor the untouched dates.Testing
New
internal/handler/clear_dates_test.gocovers all three entities: set the dates, clear them withnull(persists as NULL), and confirm that omitting a date leaves it unchanged. Fullgo test ./internal/handler ./internal/servicegreen.AI assistance
Produced with the help of Claude Code (Claude Opus 4.8). AI-assisted commits carry a
Co-Authored-Bytrailer.Summary by CodeRabbit
null).YYYY-MM-DD; invalidstart_date/target_datenow return clearer400errors.NULLand omitted fields remain unchanged.